package com.tools.middle.elasticsearch;

import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import com.tools.common.container.OwnPairs;
import com.tools.common.container.RowKit;
import com.tools.common.object.Note;
import com.tools.common.thread.Timeunit;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 多条件搜索查询。
 * 注意不能 add BoolSearch 自己本身
 * */
@Note("多条件搜索查询。" +
        "注意不能 add BoolSearch 自己本身")
public final class BoolSearch extends Search {

    @Note("AND 查询的列表")
    private List<Search> mustList;

    @Note("OR 查询的列表")
    private List<Search> shouldList;

    @Note("NOT 查询的列表")
    private List<Search> mustNotList;

    /* **********************************************************************************
     *
     *          构造器
     *
     * *********************************************************************************
     * */

    public BoolSearch(String indicesName) {
        super(indicesName);
    }

    public BoolSearch(String... indicesNames) {
        super(indicesNames);
    }

    public BoolSearch(Iterable<String> indicesNames) {
        super(indicesNames);
    }

    public static BoolSearch of(String indicesName) {
        return new BoolSearch(indicesName);
    }

    public static BoolSearch of(String... indicesNames) {
        return new BoolSearch(indicesNames);
    }

    public static BoolSearch of(Iterable<String> indicesNames) {
        return new BoolSearch(indicesNames);
    }

    /* **********************************************************************************
     *
     *          Getter / Setter
     *
     * *********************************************************************************
     * */

    List<Search> getMustList() {
        return mustList;
    }

    public List<Search> getNewMustList() {
        return mustList == null
                ? new ArrayList<>()
                : new ArrayList<>(mustList);
    }

    public void setMustList(Search search) {
        private_checkSearch(search);
        this.mustList = new ArrayList<>(1);
        this.mustList.add(search);
    }

    public void setMustList(Search... searchs) {
        int length = private_getArrayLength(searchs);
        if(length == 0) return;
        this.mustList = new ArrayList<>(length);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustList.add(s);
        }
    }

    public void setMustList(Iterable<Search> searchs) {
        if(searchs instanceof List) {
            for (Search s : searchs) private_checkSearch(s);
            this.mustList = (List<Search>) searchs;
            return;
        }
        int size = private_getIterableSize(searchs);
        if(size == 0) return;
        this.mustList = new ArrayList<>(size);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustList.add(s);
        }
    }

    List<Search> getShouldList() {
        return shouldList;
    }

    public List<Search> getNewShouldList() {
        return shouldList == null
                ? new ArrayList<>()
                : new ArrayList<>(shouldList);
    }

    public void setShouldList(Search search) {
        private_checkSearch(search);
        this.shouldList = new ArrayList<>(1);
        this.shouldList.add(search);
    }

    public void setShouldList(Search... searchs) {
        int length = private_getArrayLength(searchs);
        if(length == 0) return;
        this.shouldList = new ArrayList<>(length);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.shouldList.add(s);
        }
    }

    public void setShouldList(Iterable<Search> searchs) {
        if(searchs instanceof List) {
            for (Search s : searchs) private_checkSearch(s);
            this.shouldList = (List<Search>) searchs;
            return;
        }
        int size = private_getIterableSize(searchs);
        if(size == 0) return;
        this.shouldList = new ArrayList<>(size);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.shouldList.add(s);
        }
    }

    List<Search> getMustNotList() {
        return mustNotList;
    }

    public List<Search> getNewMustNotList() {
        return mustNotList == null
                ? new ArrayList<>()
                : new ArrayList<>(mustNotList);
    }

    public void setMustNotList(Search search) {
        private_checkSearch(search);
        this.mustNotList = new ArrayList<>(1);
        this.mustNotList.add(search);
    }

    public void setMustNotList(Search... searchs) {
        int length = private_getArrayLength(searchs);
        if(length == 0) return;
        this.mustNotList = new ArrayList<>(length);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustNotList.add(s);
        }
    }

    public void setMustNotList(Iterable<Search> searchs) {
        if(searchs instanceof List) {
            for (Search s : searchs) private_checkSearch(s);
            this.mustNotList = (List<Search>) searchs;
            return;
        }
        int size = private_getIterableSize(searchs);
        if(size == 0) return;
        this.mustNotList = new ArrayList<>(size);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustNotList.add(s);
        }
    }

    /* **********************************************************************************
     *
     *          链式调用
     *
     * *********************************************************************************
     * */

    public BoolSearch mustList(Search search) {
        this.setMustList(search);
        return this;
    }

    public BoolSearch mustList(Search... searchs) {
        this.setMustList(searchs);
        return this;
    }

    public BoolSearch mustList(Iterable<Search> searchs) {
        this.setMustList(searchs);
        return this;
    }

    public BoolSearch shouldList(Search search) {
        this.setMustList(search);
        return this;
    }

    public BoolSearch shouldList(Search... searchs) {
        this.setMustList(searchs);
        return this;
    }

    public BoolSearch shouldList(Iterable<Search> searchs) {
        this.setMustList(searchs);
        return this;
    }

    public BoolSearch mustNotList(Search search) {
        this.setMustList(search);
        return this;
    }

    public BoolSearch mustNotList(Search... searchs) {
        this.setMustList(searchs);
        return this;
    }

    public BoolSearch mustNotList(Iterable<Search> searchs) {
        this.setMustList(searchs);
        return this;
    }

    @Override
    public BoolSearch indicesNames(String indicesName) {
        super.indicesNames(indicesName);
        return this;
    }

    @Override
    public BoolSearch indicesNames(String... indicesNames) {
        super.indicesNames(indicesNames);
        return this;
    }

    @Override
    public BoolSearch indicesNames(Iterable<String> indicesNames) {
        super.indicesNames(indicesNames);
        return this;
    }

    @Override
    public BoolSearch timeout(String timeout) {
        super.timeout(timeout);
        return this;
    }

    @Override
    public BoolSearch timeout(int timeout, Timeunit timeunit) {
        super.timeout(timeout, timeunit);
        return this;
    }

    @Override
    public BoolSearch from(int from) {
        super.from(from);
        return this;
    }

    @Override
    public BoolSearch size(int size) {
        super.size(size);
        return this;
    }

    @Override
    public BoolSearch analyzer(String analyzer) {
        super.analyzer(analyzer);
        return this;
    }

    @Override
    public BoolSearch sorts(String field, SortOrder order) {
        super.sorts(field, order);
        return this;
    }

    @Override
    public BoolSearch sorts(Map<String, SortOrder> fieldAndOrders) {
        super.sorts(fieldAndOrders);
        return this;
    }

    @Override
    public BoolSearch sorts(OwnPairs<String, SortOrder> fieldAndOrders) {
        super.sorts(fieldAndOrders);
        return this;
    }

    @Override
    public BoolSearch includes(String includeField) {
        super.includes(includeField);
        return this;
    }

    @Override
    public BoolSearch includes(String... includeFields) {
        super.includes(includeFields);
        return this;
    }

    @Override
    public BoolSearch includes(Iterable<String> includeFields) {
        super.includes(includeFields);
        return this;
    }

    @Override
    public BoolSearch excludes(String excludeField) {
        super.excludes(excludeField);
        return this;
    }

    @Override
    public BoolSearch excludes(String... excludeFields) {
        super.excludes(excludeFields);
        return this;
    }

    @Override
    public BoolSearch excludes(Iterable<String> excludeFields) {
        super.excludes(excludeFields);
        return this;
    }

    @Override
    public BoolSearch aggregations(String field, Aggregation agg) {
        super.aggregations(field, agg);
        return this;
    }

    @Override
    public BoolSearch aggregations(Map<String, Aggregation> aggregations) {
        super.aggregations(aggregations);
        return this;
    }

    @Override
    public BoolSearch aggregations(OwnPairs<String, Aggregation> aggregations) {
        super.aggregations(aggregations);
        return this;
    }

    @Override
    public BoolSearch highlights(String field, HighlightField highlight) {
        super.highlights(field, highlight);
        return this;
    }

    @Override
    public BoolSearch highlights(Map<String, HighlightField> highlights) {
        super.highlights(highlights);
        return this;
    }

    @Override
    public BoolSearch highlights(OwnPairs<String, HighlightField> highlights) {
        super.highlights(highlights);
        return this;
    }

    @Override
    public String toString() {
        return "BoolSearch{" +
                "mustList=" + mustList +
                ", shouldList=" + shouldList +
                ", mustNotList=" + mustNotList +
                ", " + super.toString() +
                '}';
    }

    /* **********************************************************************************
     *
     *          自定义方法
     *
     * *********************************************************************************
     * */

    public boolean useMust() {
        return mustList != null && !mustList.isEmpty();
    }

    public boolean useShould() {
        return shouldList != null && !shouldList.isEmpty();
    }

    public boolean useMustNot() {
        return mustNotList != null && !mustNotList.isEmpty();
    }

    public BoolSearch addMust(Search search) {
        private_checkSearch(search);
        private_initMustList(1);
        this.mustList.add(search);
        return this;
    }

    public BoolSearch addMusts(Search... searchs) {
        int length = private_getArrayLength(searchs);
        if(length == 0) return this;
        private_initMustList(length);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustList.add(s);
        }
        return this;
    }

    public BoolSearch addMusts(Iterable<Search> searchs) {
        int size = private_getIterableSize(searchs);
        if(size == 0) return this;
        private_initMustList(size);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustList.add(s);
        }
        return this;
    }

    public BoolSearch modifyMust(int index, Search newSearch) {
        if(mustList != null && !mustList.isEmpty() && !default_indexCheck(index, mustList.size())) {
            private_checkSearch(newSearch);
            this.mustList.set(index, newSearch);
        }
        return this;
    }

    public BoolSearch clearMusts() {
        if(mustList != null && !mustList.isEmpty()) this.mustList.clear();
        return this;
    }

    public BoolSearch removeMust(int index) {
        if(mustList != null && !mustList.isEmpty() && !default_indexCheck(index, mustList.size())) this.mustList.remove(index);
        return this;
    }

    public BoolSearch removeMust(Search search) {
        if(mustList != null && !mustList.isEmpty()) private_removeSearch(search, mustList);
        return this;
    }

    public BoolSearch removeMusts(Search... searchs) {
        if(searchs == null || searchs.length == 0) return this;
        if(mustList != null && !mustList.isEmpty()) for (Search s: searchs) private_removeSearch(s, mustList);
        return this;
    }

    public BoolSearch removeMusts(Iterable<Search> searchs) {
        if(searchs == null) return this;
        if(mustList != null && !mustList.isEmpty()) for (Search s: searchs) private_removeSearch(s, mustList);
        return this;
    }

    public Search getMust(int index) {
        if(mustList != null && !mustList.isEmpty() && !default_indexCheck(index, mustList.size())) return this.mustList.get(index);
        return null;
    }

    public int mustSize() {
        return (mustList == null) ? 0 : mustList.size();
    }

    public boolean containsMust(Search search) {
        if(mustList != null && !mustList.isEmpty() && search != null) return this.mustList.contains(search);
        return false;
    }


    public BoolSearch addShould(Search search) {
        private_checkSearch(search);
        private_initShouldList(1);
        this.shouldList.add(search);
        return this;
    }

    public BoolSearch addShoulds(Search... searchs) {
        int length = private_getArrayLength(searchs);
        if(length == 0) return this;
        private_initShouldList(length);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.shouldList.add(s);
        }
        return this;
    }

    public BoolSearch addShoulds(Iterable<Search> searchs) {
        int size = private_getIterableSize(searchs);
        if(size == 0) return this;
        private_initShouldList(size);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.shouldList.add(s);
        }
        return this;
    }

    public BoolSearch modifyShould(int index, Search newSearch) {
        if(shouldList != null && !shouldList.isEmpty() && !default_indexCheck(index, shouldList.size())) {
            private_checkSearch(newSearch);
            this.shouldList.set(index, newSearch);
        }
        return this;
    }

    public BoolSearch clearShoulds() {
        if(shouldList != null && !shouldList.isEmpty()) this.shouldList.clear();
        return this;
    }

    public BoolSearch removeShould(int index) {
        if(shouldList != null && !shouldList.isEmpty() && !default_indexCheck(index, shouldList.size())) this.shouldList.remove(index);
        return this;
    }

    public BoolSearch removeShould(Search search) {
        if(shouldList != null && !shouldList.isEmpty()) private_removeSearch(search, shouldList);
        return this;
    }

    public BoolSearch removeShoulds(Search... searchs) {
        if(searchs == null || searchs.length == 0) return this;
        if(shouldList != null && !shouldList.isEmpty()) for (Search s: searchs) private_removeSearch(s, shouldList);
        return this;
    }

    public BoolSearch removeShoulds(Iterable<Search> searchs) {
        if(searchs == null) return this;
        if(shouldList != null && !shouldList.isEmpty()) for (Search s: searchs) private_removeSearch(s, shouldList);
        return this;
    }

    public Search getShould(int index) {
        if(shouldList != null && !shouldList.isEmpty() && !default_indexCheck(index, shouldList.size())) return this.shouldList.get(index);
        return null;
    }

    public int shouldSize() {
        return (shouldList == null) ? 0 : shouldList.size();
    }

    public boolean containsShould(Search search) {
        if(shouldList != null && !shouldList.isEmpty() && search != null) return this.shouldList.contains(search);
        return false;
    }


    public BoolSearch addMustNot(Search search) {
        private_checkSearch(search);
        private_initMustNotList(1);
        this.mustNotList.add(search);
        return this;
    }

    public BoolSearch addMustNots(Search... searchs) {
        int length = private_getArrayLength(searchs);
        if(length == 0) return this;
        private_initMustNotList(length);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustNotList.add(s);
        }
        return this;
    }

    public BoolSearch addMustNots(Iterable<Search> searchs) {
        int size = private_getIterableSize(searchs);
        if(size == 0) return this;
        private_initMustNotList(size);
        for (Search s : searchs) {
            private_checkSearch(s);
            this.mustNotList.add(s);
        }
        return this;
    }

    public BoolSearch modifyMustNot(int index, Search newSearch) {
        if(mustNotList != null && !mustNotList.isEmpty() && !default_indexCheck(index, mustNotList.size())) {
            private_checkSearch(newSearch);
            this.mustNotList.set(index, newSearch);
        }
        return this;
    }

    public BoolSearch clearMustNots() {
        if(mustNotList != null && !mustNotList.isEmpty()) this.mustNotList.clear();
        return this;
    }

    public BoolSearch removeMustNot(int index) {
        if(mustNotList != null && !mustNotList.isEmpty() && !default_indexCheck(index, mustNotList.size())) this.mustNotList.remove(index);
        return this;
    }

    public BoolSearch removeMustNot(Search search) {
        if(mustNotList != null && !mustNotList.isEmpty()) private_removeSearch(search, mustNotList);
        return this;
    }

    public BoolSearch removeMustNots(Search... searchs) {
        if(searchs == null || searchs.length == 0) return this;
        if(mustNotList != null && !mustNotList.isEmpty()) for (Search s: searchs) private_removeSearch(s, mustNotList);
        return this;
    }

    public BoolSearch removeMustNots(Iterable<Search> searchs) {
        if(searchs == null) return this;
        if(mustNotList != null && !mustNotList.isEmpty()) for (Search s: searchs) private_removeSearch(s, mustNotList);
        return this;
    }

    public Search getMustNot(int index) {
        if(mustNotList != null && !mustNotList.isEmpty() && !default_indexCheck(index, mustNotList.size())) return this.mustNotList.get(index);
        return null;
    }

    public int mustNotSize() {
        return (mustNotList == null) ? 0 : mustNotList.size();
    }

    public boolean containsMustNot(Search search) {
        if(mustNotList != null && !mustNotList.isEmpty() && search != null) return this.mustNotList.contains(search);
        return false;
    }

    @Override
    public BoolSearch addIndicesName(String indicesName) {
        super.addIndicesName(indicesName);
        return this;
    }

    @Override
    public BoolSearch addIndicesNames(String... indicesNames) {
        super.addIndicesNames(indicesNames);
        return this;
    }

    @Override
    public BoolSearch addIndicesNames(Iterable<String> indicesNames) {
        super.addIndicesNames(indicesNames);
        return this;
    }

    @Override
    public BoolSearch modifyIndicesName(int index, String indicesName) {
        super.modifyIndicesName(index, indicesName);
        return this;
    }

    @Override
    public BoolSearch removeIndicesName(String indicesName) {
        super.removeIndicesName(indicesName);
        return this;
    }

    @Override
    public BoolSearch removeIndicesName(int index) {
        super.removeIndicesName(index);
        return this;
    }

    @Override
    public BoolSearch removeIndicesNames(String... indicesNames) {
        super.removeIndicesNames(indicesNames);
        return this;
    }

    @Override
    public BoolSearch removeIndicesNames(Iterable<String> indicesNames) {
        super.removeIndicesNames(indicesNames);
        return this;
    }

    @Override
    public BoolSearch addSort(String field, SortOrder order) {
        super.addSort(field, order);
        return this;
    }

    @Override
    public BoolSearch addSorts(Map<String, SortOrder> fieldAndOrders) {
        super.addSorts(fieldAndOrders);
        return this;
    }

    @Override
    public BoolSearch addSorts(OwnPairs<String, SortOrder> fieldAndOrders) {
        super.addSorts(fieldAndOrders);
        return this;
    }

    @Override
    public BoolSearch modifySort(int index, String field, SortOrder order) {
        super.modifySort(index, field, order);
        return this;
    }

    @Override
    public BoolSearch clearSorts() {
        super.clearSorts();
        return this;
    }

    @Override
    public BoolSearch removeSort(String field, SortOrder order) {
        super.removeSort(field, order);
        return this;
    }

    @Override
    public BoolSearch removeSort(int index) {
        super.removeSort(index);
        return this;
    }

    @Override
    public BoolSearch removeSorts(Map<String, SortOrder> fieldAndOrders) {
        super.removeSorts(fieldAndOrders);
        return this;
    }

    @Override
    public BoolSearch removeSorts(OwnPairs<String, SortOrder> fieldAndOrders) {
        super.removeSorts(fieldAndOrders);
        return this;
    }

    @Override
    public BoolSearch addInclude(String field) {
        super.addInclude(field);
        return this;
    }

    @Override
    public BoolSearch addIncludes(String... fields) {
        super.addIncludes(fields);
        return this;
    }

    @Override
    public BoolSearch addIncludes(Iterable<String> fields) {
        super.addIncludes(fields);
        return this;
    }

    @Override
    public BoolSearch modifyInclude(int index, String field) {
        super.modifyInclude(index, field);
        return this;
    }

    @Override
    public BoolSearch clearIncludes() {
        super.clearIncludes();
        return this;
    }

    @Override
    public BoolSearch removeInclude(String field) {
        super.removeInclude(field);
        return this;
    }

    @Override
    public BoolSearch removeInclude(int index) {
        super.removeInclude(index);
        return this;
    }

    @Override
    public BoolSearch removeIncludes(String... fields) {
        super.removeIncludes(fields);
        return this;
    }

    @Override
    public BoolSearch removeIncludes(Iterable<String> fields) {
        super.removeIncludes(fields);
        return this;
    }

    @Override
    public BoolSearch addExclude(String field) {
        super.addExclude(field);
        return this;
    }

    @Override
    public BoolSearch addExcludes(String... fields) {
        super.addExcludes(fields);
        return this;
    }

    @Override
    public BoolSearch addExcludes(Iterable<String> fields) {
        super.addExcludes(fields);
        return this;
    }

    @Override
    public BoolSearch modifyExclude(int index, String field) {
        super.modifyExclude(index, field);
        return this;
    }

    @Override
    public BoolSearch clearExcludes() {
        super.clearExcludes();
        return this;
    }

    @Override
    public BoolSearch removeExclude(String field) {
        super.removeExclude(field);
        return this;
    }

    @Override
    public BoolSearch removeExclude(int index) {
        super.removeExclude(index);
        return this;
    }

    @Override
    public BoolSearch removeExcludes(String... fields) {
        super.removeExcludes(fields);
        return this;
    }

    @Override
    public BoolSearch removeExcludes(Iterable<String> fields) {
        super.removeExcludes(fields);
        return this;
    }

    @Override
    public BoolSearch addAgg(String field, Aggregation agg) {
        super.addAgg(field, agg);
        return this;
    }

    @Override
    public BoolSearch addAggs(Map<String, Aggregation> aggs) {
        super.addAggs(aggs);
        return this;
    }

    @Override
    public BoolSearch addAggs(OwnPairs<String, Aggregation> aggs) {
        super.addAggs(aggs);
        return this;
    }

    @Override
    public BoolSearch clearAggs() {
        super.clearAggs();
        return this;
    }

    @Override
    public BoolSearch removeAgg(String field) {
        super.removeAgg(field);
        return this;
    }

    @Override
    public BoolSearch removeAgg(String... fields) {
        super.removeAgg(fields);
        return this;
    }

    @Override
    public BoolSearch removeAgg(Iterable<String> fields) {
        super.removeAgg(fields);
        return this;
    }

    @Override
    public BoolSearch addHighlight(String field, HighlightField highlight) {
        super.addHighlight(field, highlight);
        return this;
    }

    @Override
    public BoolSearch addHighlights(Map<String, HighlightField> highlights) {
        super.addHighlights(highlights);
        return this;
    }

    @Override
    public BoolSearch addHighlights(OwnPairs<String, HighlightField> highlights) {
        super.addHighlights(highlights);
        return this;
    }

    @Override
    public BoolSearch clearHighlight() {
        super.clearHighlight();
        return this;
    }

    @Override
    public BoolSearch removeHighlight(String field) {
        super.removeHighlight(field);
        return this;
    }

    @Override
    public BoolSearch removeHighlights(String... fields) {
        super.removeHighlights(fields);
        return this;
    }

    @Override
    public BoolSearch removeHighlights(Iterable<String> fields) {
        super.removeHighlights(fields);
        return this;
    }
    
    
    /* **********************************************************************************
     *
     *          私有逻辑
     *
     * *********************************************************************************
     * */

    @Note("搜索查询实例的校验")
    private static void private_checkSearch(Search search) {
        if(search == null) throw new NullPointerException();
        if(search instanceof BoolSearch) throw new IllegalArgumentException("BoolSearch 不能添加自己本身作为查询条件");
    }

    @Note("获取数组容器的容量")
    private static int private_getArrayLength(Search[] array) {
        return (array == null) ? 0 : array.length;
    }

    @Note("获取可迭代容器的容量")
    private static int private_getIterableSize(Iterable<Search> iterable) {
        return RowKit.getIterableSize(iterable);
    }

    @Note("初始化 AND 查询列表")
    private void private_initMustList(int initSize) {
        if(mustList == null) this.mustList = new ArrayList<>(initSize);
    }

    @Note("初始化 OR 查询列表")
    private void private_initShouldList(int initSize) {
        if(shouldList == null) this.shouldList = new ArrayList<>(initSize);
    }

    @Note("初始化 NOT 查询列表")
    private void private_initMustNotList(int initSize) {
        if(mustNotList == null) this.mustNotList = new ArrayList<>(initSize);
    }

    @Note("移除列表中的元素。若元素为 null 则忽略")
    private static void private_removeSearch(Search search, List<Search> list) {
        if(search == null) return;
        list.remove(search);
    }
}
